// infer 类型推导

// typeof、keyof
// typeof（在 JavaScript 中，typeof 是一个操作符，用于返回一个表示数据类型的字符串。）
// 返回值 number、string、boolean、object、function、undefined、symbol、bigint。
let operand = ''
typeof operand;
typeof (operand); // 带括号的形式（功能相同）

// keyof（keyof 是 TypeScript 中的一个类型操作符，用于获取对象类型的所有键组成的联合类型。）
type User = {
    name: string;
    age: number;
    email: string;
};

type UserKeys = keyof User; // "name" | "age" | "email"

function getProperty(obj: User, key: UserKeys) {
    return obj[key];
}

const user: User = { name: "Alice", age: 30, email: "alice@example.com" };
getProperty(user, "age"); // 正确
// getProperty(user, "invalidKey"); // 错误：类型 '"invalidKey"' 不能赋值给类型 'UserKeys'

// instanceof:判断一个对象是否是某个类的实例
class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const p1 = new Person('张三', 18);
p1 instanceof Person; // true
p1 instanceof Object; // true

// in
'name' in p1; // true
'age' in p1; // true
'email' in p1; // false
'getName' in p1; // false

// 自定义类型保护函数
interface Flower {
    bloom(): void;
    germinate(): void;
}

interface Tree {
    shade(): void;
    germinate(): void;
}

function isFlower(flower: Flower | Tree): flower is Flower {
    return (flower as Flower).bloom !== undefined;
}

function move(flower: Flower | Tree) {
    if (isFlower(flower)) {
        flower.bloom();
    } else {
        flower.shade();
    }
}

const flower: Flower = {
    bloom() { console.log("Blooming"); },
    germinate() { console.log("Germinating"); }
}

const tree: Tree = {
    shade() { console.log("Shading"); },
    germinate() { console.log("Germinating"); }
}

move(flower); // "Blooming"​ 
move(tree); // "Shading"

// null 与 undefined 类型保护
function isNullOrUndefined<T>(value: T): value is NonNullable<T> {
    return value === null || value === undefined;
}
const a: string | null | undefined = '123';
console.log(isNullOrUndefined(a)) // false
console.log(isNullOrUndefined(null)) // true
console.log(isNullOrUndefined(undefined)) // true


// 简单类型推导
type ReturnType<T> = T extends (...args: any[]) => infer R
    ? R
    : any;

function sayHello(): string {
    return 'hello';
}

type HelloReturnType = ReturnType<typeof sayHello>; // string
let helloReturn: HelloReturnType;

// EleType 类型用于提取数组的元素类型：​   
// T extends (infer U)[]：表示如果 T 是一个数组类型，则使用 infer U 提取其元素类型 U。​    
// ? U : T：如果 T 是数组类型，结果类型为 U，否则为 T。
type EleType<T> = T extends (infer U)[] ? U : T;
type StringArray = string[];
type NumberArray = number[];
type MixedArray = (string | number)[];
type MixedBase = string | boolean;
type StringEle = EleType<StringArray>; // string​ 
type NumberEle = EleType<NumberArray>; // number​ 
type MixedEle = EleType<MixedArray>;   // string | number
type MixedBaseEle = EleType<MixedBase>; // string | boolean

// ​ First 类型用于提取元组的第一个元素类型：​    
//  T extends [infer U, ...any[]]：表示如果 T 是一个包含至少一个元素的元组类型，则使用 infer U 提取其第一个元素的类型 U。​    
// ? U : never：如果 T 是元组类型，结果类型为 U，否则为 never。
type First<T> = T extends [infer U, ...any[]] ? U : never;
type Tuple1 = [string, number, boolean];
type Tuple2 = [number, boolean];
type FirstElement1 = First<Tuple1>; // string​ 
type FirstElement2 = First<Tuple2>; // number​    ​

// Parameters 类型用于提取函数的参数类型：
// T extends (...args: infer P) => any：表示如果 T 是一个函数类型，则使用 infer P 提取其参数类型 P。
// ? P : never：如果 T 是函数类型，结果类型为 P，否则为 never。
type Parameters1<T> = T extends (...args: infer P) => any ? P : never;
type Fn1 = (a: number, b: string) => void;
type FnParams1 = Parameters1<Fn1>; // [number, string]

type Parameters2<T> = T extends (...args: infer P) => any ? P : never;
function fun(arg1: string, arg2: number): void { }
type FnParams2 = Parameters2<typeof fun>; // FnParams 的类型是 [string, number]

// 假设我们现在要拿到某个函数的返回值的类型
function sub(a: number, b: number) {
    return a - b;
}

// 第一步先确定函数的整体类型
type SubType = typeof sub;

// 第二步，确定你需要去提取的类型所在的位置
type PickReturnType<Fn> = Fn extends (a: number, b: number) => infer R
    ? R
    : never;

let f: PickReturnType<SubType>; // f 推导为 number
f = 3;
// f = '3' // Type 'string' is not assignable to type 'number'.

// 提取参数类型
type PickParamsType<Fn> = Fn extends (...args: infer R) => any
    ? R
    : never;
let p: PickParamsType<SubType>; // p 推导为 [number, number]
p = [1, 2];
// p = () => [1, 2] // Type '() => number[]' is not assignable to type '[a: number, b: number]'.
// p = [1] // Type '[number]' is not assignable to type '[a: number, b: number]'. Source has 1 element(s) but target requires 2.
// p = ['2'] // Type 'string' is not assignable to type 'number'.